package com.mybpm.core.definition


import groovy.xml.XmlParser
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

/**
 * 流程解析服务
 */
@Service
public class ProcessParseService {
    private static final Logger log = LoggerFactory.getLogger(ProcessParseService.class)

    public static final List processProperties = ['id', 'name', 'displayName', 'version']
    public static final List startProperties = ['id', 'name', 'displayName', 'actor']
    public static final List endProperties = ['id', 'name', 'displayName']
    public static final List nodeProperties = ['id', 'name', 'displayName', 'actor']
    public static final List transitionProperties = ['id', 'name', 'displayName', 'to', 'toName', 'actor']

    /**
     * 拆分流程定义字符串，并获得流程定义信息<p>
     *
     * @params xml            流程定义
     */
    public Map parse(String xml) {
        Map process = [:]
        groovy.util.Node procDef = new XmlParser().parseText(xml)
        XmlProcess xmlProcess = new XmlProcess()
        xmlProcess.hashcode = xml.hashCode()                    // 记录当前XML字符串的hashcode
        xmlProcess.timestamp = System.currentTimeMillis()    // 记录当前时间戳
        // TODO 其它属性
        addProps(xmlProcess, procDef, processProperties)
        xmlProcess.id = generateId(xmlProcess.id, xmlProcess.name)
        log.debug("-------parse:{}", xmlProcess.properties)
        process.process = xmlProcess
        parseStart(procDef, process)     // 解析开始环节
        parseEnd(procDef, process)       // 解析结束环节
        parseNode(procDef, process)      // 解析其它环节
        transition2Node(process)         // 关联路径的目标环节
        return process
    }

    /**
     * 解析开始环节
     */
    private void parseStart(groovy.util.Node procDef, Map process) {
        groovy.util.Node startNode = procDef.'start'[0]    //限制:开始节点只有一个
        XmlNode start = new XmlNode(type: 'start')
        // TODO 其它属性
        addProps(start, startNode, startProperties)
        start.id = generateId(start.id, start.name)
//        start.multitranAuto = startNode.attribute('multitran')
//        if (proc.enableI18n) startEvent.i18nCodePrefix = proc.i18nCodePrefix + '.' + startEvent.id
//        startEvent.processId = proc.id
//        proc.startEvents.put(startEvent.id, startEvent)
        process.start = start
        // 路径
        parseTransition(startNode, start, process)
//        // 校验test是否设置正确：
//        // 如果有test条件则只能是最后一个指向首环节的路径无test条件，否则通过控制器设置request.processStartNode
//        for (StartEvent se in proc.startEvents.values()) {
//            if (se.sequenceFlowsOut.size() > 1) {
//                boolean flag = se.sequenceFlowsOut[0].test
//                for (int i = 1 i < se.sequenceFlowsOut.size() i ++) {
//                    SequenceFlow sf = se.sequenceFlowsOut[i]
//                    if (!flag && sf.test) {
//                        throw new RuntimeException("start-state中的路径要么都设置test条件（互斥，最后一个可不设），要么都不设置：" + sf.toName + " @ " + dataClass)
//                    } else if (flag && !sf.test && i + 1 < se.sequenceFlowsOut.size()) {
//                        throw new RuntimeException("start-state中的路径要么都设置test条件（互斥，最后一个可不设），要么都不设置：" + sf.toName + " @ " + dataClass)
//                    }
//                }
//            }
//        }
    }

    /**
     * 解析结束环节
     */
    private void parseEnd(groovy.util.Node procDef, Map process) {
        groovy.util.Node endtNode = procDef.'end'[0]    //限制:结束节点只有一个
        XmlNode end = new XmlNode(type: 'end')
        // TODO 其它属性
        addProps(end, endtNode, endProperties)
        end.id = generateId(end.id, end.name)
        process.end = end
    }

    /**
     * 解析环节
     */
    private void parseNode(groovy.util.Node procDef, Map process) {
        process.nodes = [:]
        // 遍历环节
        for (groovy.util.Node n in procDef.'node') {
            XmlNode xmlNode = new XmlNode(type: 'node')
            // TODO 其它属性
            addProps(xmlNode, n, nodeProperties)
            xmlNode.id = generateId(xmlNode.id, xmlNode.name)
            process.nodes[xmlNode.id] = xmlNode
            // 路径
            parseTransition(n, xmlNode, process)
        }
    }

    /**
     * 解析路径
     */
    private void parseTransition(groovy.util.Node node, XmlNode xmlNode, Map process) {
        // 路径
        xmlNode.transitions = []
        if (!process.containsKey("transitions")) process.transitions = [:]
        for (groovy.util.Node t in node.transition) {
            XmlTransition xmlTransition = new XmlTransition()
            // TODO 其它属性
            addProps(xmlTransition, t, transitionProperties)
            xmlTransition.id = generateId(xmlTransition.id, xmlTransition.name)
            // TODO 路径id不存在时，需要计算
            if (!xmlTransition.id) {
                xmlTransition.id = xmlNode.id + "-" + generateId(xmlTransition.to, xmlTransition.toName)
            }
            xmlTransition.fromNode = xmlNode
            xmlNode.transitions.add(xmlTransition)
            process.transitions[xmlTransition.id] = xmlTransition
        }
    }

    /**
     * 关联路径的目标环节
     */
    private void transition2Node(Map process) {
        // 路径
        ((Map<String, XmlTransition>) process.transitions).each { k, v ->
            String nodeId = generateId(v.to, v.toName)
            v.toNode = process.nodes[nodeId]
            if (!v.toNode && nodeId.equals(process.end.id)) v.toNode = process.end
        }
    }

    private String generateId(String id, String name) {
        if (id) {
            return id
        } else if (name) {
            return new String(Base64Encode(name.getBytes("UTF-8"))).trim()
        } else {
            return null
        }
    }

    /** 批量添加属性 */
    private void addProps(bean, groovy.util.Node n, List<String> names) {
        for (String name in names) {
            String val = n.attribute(name)
            bean[name] = val;
        }
    }

    /**
     * 功能：编码byte[]
     *
     * @param data 源
     * @return char[]
     */
    private char[] Base64Encode(byte[] data) {
        char[] out = new char[((data.length + 2) / 3) * 4];
        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
            boolean quad = false;
            boolean trip = false;

            int val = (0xFF & (int) data[i]);
            val <<= 8;
            if ((i + 1) < data.length) {
                val |= (0xFF & (int) data[i + 1]);
                trip = true;
            }
            val <<= 8;
            if ((i + 2) < data.length) {
                val |= (0xFF & (int) data[i + 2]);
                quad = true;
            }
            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 1] = alphabet[val & 0x3F];
            val >>= 6;
            out[index + 0] = alphabet[val & 0x3F];
        }
        return out;
    }
    private static char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray();
}